package br.org.nomoney.vink.util.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import br.org.nomoney.vink.util.serialize.ObjectToFromBytesConverter;
import br.org.nomoney.vink.util.servlet.io.ByteArrayUtil;

@SuppressWarnings("serial")
public abstract class MethodCallerServlet extends HttpServlet implements
		ObjectToFromBytesConverter {

	public void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {

		String methodName = req.getParameter("method");

		// Response
		byte[] outData = null;

		if (methodName != null && !methodName.equals("")) {

			try {
				// Get arguments as bytes
				InputStream is = req.getInputStream();
				int length = req.getContentLength();
				byte[] inContent = ByteArrayUtil.getByteArrayFromInput(length,
						is);

				// Transform them to Object[]
				Object[] arguments = null;
				Object o = fromBytesToObject(inContent);
				if (o != null && o instanceof Object[]) {
					arguments = (Object[]) o;
				}

				// Execute method and get result
				Object response = executeMethod(getInterfaceInstance(),
						methodName, arguments);

				// Transform result to bytes
				outData = fromObjectToBytes(response);
			} catch (Exception e) {
				if (e instanceof InvocationTargetException) {
					InvocationTargetException ite = (InvocationTargetException) e;
					e = (Exception) ite.getTargetException();
				}
				outData = fromObjectToBytes(e);
			} finally {
				// Send response to client
				writeBytesAtResponse(resp, outData);
			}

		} else {

			// Send error to client
			Exception e = new NoSuchMethodException(
					"Invalid method name was sent: " + methodName);
			outData = fromObjectToBytes(e);
			writeBytesAtResponse(resp, outData);

		}
		resp.flushBuffer();
	}

	public abstract Object getInterfaceInstance();

	private void writeBytesAtResponse(HttpServletResponse resp, byte[] outData)
			throws IOException {

		resp.setContentType("application/octet-stream");
		OutputStream out = resp.getOutputStream();

		out.write(outData);
		out.close();
	}

	private Object executeMethod(Object inter, String methodName, Object[] args)
			throws SecurityException, NoSuchMethodException,
			IllegalArgumentException, IllegalAccessException,
			InvocationTargetException {

		Class<?>[] parametersTypes = getMethodParametersTypes(args);
		Method m = inter.getClass().getMethod(methodName, parametersTypes);
		return m.invoke(inter, args);
	}

	private Class<?>[] getMethodParametersTypes(Object[] args) {
		Class<?>[] parameterTypes = null;
		if (args != null) {
			parameterTypes = new Class<?>[args.length];
			int pos = 0;
			for (Object object : args) {
				parameterTypes[pos++] = object.getClass();
			}
		}
		return parameterTypes;
	}

}